package com.hyzx.qbasic.admin.domain.service.impl;

import com.github.pagehelper.PageInfo;
import com.hyzx.qbasic.admin.common.security.ShiroFilerChainManager;
import com.hyzx.qbasic.admin.common.security.UrlFilter;
import com.hyzx.qbasic.admin.domain.service.IPermissionService;
import com.hyzx.qbasic.admin.model.constant.CacheKeyConst;
import com.hyzx.qbasic.admin.model.dto.*;
import com.hyzx.qbasic.admin.model.enums.ResourceType;
import com.hyzx.qbasic.admin.model.po.ResourceInfoPO;
import com.hyzx.qbasic.admin.model.po.ResourcePO;
import com.hyzx.qbasic.admin.model.vo.req.admin.AddResourceReq;
import com.hyzx.qbasic.admin.model.vo.req.admin.DeleteResourceReq;
import com.hyzx.qbasic.admin.model.vo.req.admin.EditResourceReq;
import com.hyzx.qbasic.admin.model.vo.req.admin.GetResourceListReq;
import com.hyzx.qbasic.admin.model.vo.res.BaseOperationRes;
import com.hyzx.qbasic.admin.common.util.OperationResUtils;
import com.hyzx.qbasic.common.util.TreeNode;
import com.hyzx.qbasic.common.util.TreeNodeUtils;
import com.hyzx.qbasic.admin.domain.repository.IResourceRepository;
import com.hyzx.qbasic.admin.domain.repository.IRoleRepository;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Created by 黄江华 on 2017/10/9
 */

@Service
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS) //使用缓存需加此注解
public class PermissionServiceImpl implements IPermissionService {
    @Autowired
    private ShiroFilerChainManager shiroFilerChainManager;

    @Autowired
    private IRoleRepository roleRepository;

    @Autowired
    private IResourceRepository resourceRepository;

    @Override
    @Cacheable(cacheNames = CacheKeyConst.PERMISSION_LIST_CACHE_NAME, key = "#userId")
    public AuthorizationInfo getUserAuthorizationInfo(int userId) {
        AuthorizationInfo authorizationInfo = new AuthorizationInfo();

        Set<String> roleList = roleRepository.getUserRoleNameList(userId);
        if (CollectionUtils.isEmpty(roleList))
            return authorizationInfo;

        authorizationInfo.setRoleList(roleList);

        Set<String> permissionList = resourceRepository.getPermissionList(roleList);
        authorizationInfo.setPermissionList(permissionList);

        return authorizationInfo;
    }

    @Override
    public List<MenuListInfo> getUserMenuList(int userId) {
        Set<String> roleList = roleRepository.getUserRoleNameList(userId);
        if (CollectionUtils.isEmpty(roleList))
            return Collections.emptyList();

        List<ResourcePO> resourceList = resourceRepository.getMenuList(roleList);
        if (CollectionUtils.isEmpty(resourceList))
            return Collections.emptyList();

        List<ResourcePO> parentResList = resourceList.stream()
                .filter(res -> res.getType().equals(ResourceType.MODULE.ordinal()))
                .collect(Collectors.toList());

        List<MenuListInfo> menuList = new ArrayList<>(parentResList.size());
        for (ResourcePO parentRes : parentResList) {
            List<ResourcePO> subResList = resourceList.stream()
                    .filter(res -> res.getType().equals(ResourceType.MENU.ordinal())
                            && res.getParentId().equals(parentRes.getResourceId()))
                    .collect(Collectors.toList());

            if (CollectionUtils.isEmpty(subResList))
                continue;

            MenuListInfo menuInfo = new MenuListInfo();
            menuInfo.setParentMenu(mapToMenuInfo(parentRes));

            List<MenuInfo> subMenuList = new ArrayList<>(subResList.size());
            for (ResourcePO subRes : subResList) {
                subMenuList.add(mapToMenuInfo(subRes));
            }
            menuInfo.setSubMenuList(subMenuList);

            menuList.add(menuInfo);
        }

        return menuList;
    }

    @Override
    public List<MenuInfo> getUserFunctionList(int userId) {
        Set<String> roleList = roleRepository.getUserRoleNameList(userId);
        if (CollectionUtils.isEmpty(roleList))
            return Collections.emptyList();

        List<ResourcePO> resourceList = resourceRepository.getFunctionList(roleList);
        if (CollectionUtils.isEmpty(resourceList))
            return Collections.emptyList();

        List<MenuInfo> functionList = new ArrayList<>(resourceList.size());
        resourceList.forEach(resource -> functionList.add(mapToMenuInfo(resource)));

        return functionList;
    }

    @Override
    public Optional<MenuInfo> getFunction(int userId, String permission) {
        List<MenuInfo> functionList = getUserFunctionList(userId);
        if (CollectionUtils.isEmpty(functionList))
            return Optional.empty();

        Optional<MenuInfo> function = functionList.stream()
                .filter(func -> func.getPermission().equals(permission))
                .findFirst();

        return function;
    }

    @Override
    public List<ResourceTreeInfo> getAllResourceList() {
        List<ResourcePO> poList = resourceRepository.getAllResourceList();
        if (CollectionUtils.isEmpty(poList))
            return Collections.emptyList();

        List<ResourceTreeInfo> treeInfoList = new ArrayList<>(poList.size());
        poList.forEach(po -> {
            ResourceTreeInfo treeInfo = new ResourceTreeInfo();
            BeanUtils.copyProperties(po, treeInfo);

            treeInfoList.add(treeInfo);
        });

        return treeInfoList;
    }

    @Override
    public PageInfo<ResourceInfoPO> getPagedResourceList(GetResourceListReq req) {
        PageInfo<ResourceInfoPO> resourceList = resourceRepository.getPagedResourceList(
                req.getPageNum(), req.getPageSize(), req.getResourceName(), req.getResourceId());

        return resourceList;
    }

    @Override
    public BaseOperationRes addResource(AddResourceReq req) {
        ResourcePO resourcePO = new ResourcePO();
        resourcePO.setName(req.getName());
        resourcePO.setPermission(req.getPermission());
        resourcePO.setType(req.getType());
        resourcePO.setPriority(req.getPriority());
        resourcePO.setParentId(req.getParentIdList().get(req.getParentIdList().size() - 1));
        resourcePO.setIconClass(req.getIconClass());
        resourcePO.setUrl(req.getType().equals(ResourceType.MODULE.ordinal()) ? null : req.getUrl());
        resourcePO.setCreateTime(new Date());
        resourcePO.setUpdateTime(new Date());

        boolean success = resourceRepository.addResource(resourcePO);
        if (!success)
            return OperationResUtils.fail("新增资源失败");

        initFilterChain();

        return OperationResUtils.success();
    }

    @Override
    public ResourcePO getResourceInfo(int resourceId) {
        return resourceRepository.getResourceInfo(resourceId);
    }

    @Override
    @CacheEvict(cacheNames = CacheKeyConst.PERMISSION_LIST_CACHE_NAME, allEntries = true)
    public BaseOperationRes editResource(EditResourceReq req) {
        //检查资源是否存在
        ResourcePO existedResource = resourceRepository.getResourceInfo(req.getResourceId());
        if (existedResource == null)
            return OperationResUtils.fail("资源不存在");

        ResourcePO resourcePO = new ResourcePO();
        resourcePO.setResourceId(req.getResourceId());
        resourcePO.setName(req.getName());
        resourcePO.setPermission(req.getPermission());
        resourcePO.setType(req.getType());
        resourcePO.setPriority(req.getPriority());
        resourcePO.setParentId(req.getParentIdList().get(req.getParentIdList().size() - 1));
        resourcePO.setIconClass(req.getIconClass());
        resourcePO.setUrl(req.getType().equals(ResourceType.MODULE.ordinal()) ? null : req.getUrl());
        resourcePO.setUpdateTime(new Date());

        boolean success = resourceRepository.editResource(resourcePO);
        if (!success)
            return OperationResUtils.fail("编辑资源信息失败");

        initFilterChain();

        return OperationResUtils.success();
    }

    @Override
    @CacheEvict(cacheNames = CacheKeyConst.PERMISSION_LIST_CACHE_NAME, allEntries = true)
    public BaseOperationRes deleteResource(DeleteResourceReq req) {
        List<ResourcePO> resourceList = resourceRepository.getAllResourceList();
        List<TreeNode> treeNodeList = resourceList.stream()
                .map(resource -> new TreeNode(resource.getResourceId(), resource.getName(), resource.getParentId()))
                .collect(Collectors.toList());

        List<Integer> childList = new ArrayList<>(resourceList.size());

        for (Integer resourceId : req.getResourceIdList()) {
            childList.addAll(TreeNodeUtils.getChildNodes(treeNodeList, resourceId));
        }

        boolean success = resourceRepository.deleteResource(childList.stream().distinct().collect(Collectors.toList()));
        if (!success)
            return OperationResUtils.fail("删除资源失败");

        initFilterChain();

        return OperationResUtils.success();
    }

    @Override
    public List<UrlFilter> getAllUrlFilterList() {
        List<ResourcePO> resourceList = resourceRepository.getAllResourceList();
        if (CollectionUtils.isEmpty(resourceList))
            return Collections.emptyList();

        List<UrlFilter> urlFilterList = new ArrayList<>(resourceList.size());
        resourceList.stream()
                .filter(resource -> resource.getType() != ResourceType.MODULE.ordinal())
                .forEach(resource -> {
                    UrlFilter urlFilter = new UrlFilter();
                    urlFilter.setName(resource.getName());
                    urlFilter.setUrl(resource.getUrl());
                    urlFilter.setPermission(resource.getPermission());

                    urlFilterList.add(urlFilter);
                });

        return urlFilterList;
    }

    @PostConstruct
    public void initFilterChain() {
        shiroFilerChainManager.initFilterChains(getAllUrlFilterList());
    }

    private MenuInfo mapToMenuInfo(ResourcePO po) {
        MenuInfo bo = new MenuInfo();
        bo.setName(po.getName());
        bo.setPermission(po.getPermission());
        bo.setUrl(po.getUrl());
        bo.setIconClass(po.getIconClass());

        return bo;
    }
}
